package org.polyforms.repository.executor.support;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.polyforms.repository.ReadOnlyRepository;
import org.polyforms.repository.executor.ClassBasedExecutor;
import org.polyforms.repository.factory.EntityClassResolver;
import org.polyforms.repository.spi.IdentifierHelper;
import org.springframework.util.ReflectionUtils;

/**
 * Implementation of {@link ReadOnlyRepository} which uses JPA as persistence framework.
 * 
 * @author Kuisong Tong
 * @since 1.0
 */
@Named
public final class ReadOnlyRepositoryExecutor implements ClassBasedExecutor {
    private final Map<Class<?>, JpaRepository> jpaRepositories = new HashMap<Class<?>, JpaRepository>();

    @PersistenceContext
    private EntityManager entityManager;

    private final EntityClassResolver entityClassResolver;

    private final IdentifierHelper identifierHelper;

    /**
     * Create an instance with {@link EntityClassResolver}s.
     */
    @Inject
    public ReadOnlyRepositoryExecutor(final EntityClassResolver entityClassResolver,
            final IdentifierHelper identifierHelper) {
        this.entityClassResolver = entityClassResolver;
        this.identifierHelper = identifierHelper;
    }

    /**
     * {@inheritDoc}
     */

    public Class<?> getMatchedInterface() {
        return ReadOnlyRepository.class;
    }

    /**
     * {@inheritDoc}
     */
    public Object execute(final Object target, final Method method, final Object... arguments) throws Throwable {
        try {
            return method.invoke(getRepository(target), arguments);
        } catch (final Exception e) {
            ReflectionUtils.handleReflectionException(e);
        }

        throw new IllegalStateException("Should never get here");
    }

    private JpaRepository getRepository(final Object target) {
        final Class<?> entityClass = entityClassResolver.resolve(target.getClass());
        if (!jpaRepositories.containsKey(entityClass)) {
            jpaRepositories.put(entityClass, new JpaRepository(entityClass));
        }
        return jpaRepositories.get(entityClass);
    }

    private final class JpaRepository implements ReadOnlyRepository<Object, Object> {
        private final Class<?> entityClass;

        protected JpaRepository(final Class<?> entityClass) {
            this.entityClass = entityClass;
        }

        /**
         * {@inheritDoc}
         */
        public Object get(final Object identifier) {
            if (identifier == null) {
                throw new IllegalArgumentException("Parameter identifier (Object) must not be null.");
            }
            return entityManager.find(entityClass, identifier);
        }

        /**
         * {@inheritDoc}
         */
        @SuppressWarnings("unchecked")
        public List<Object> load() {
            return entityManager.createQuery(getSelectClause()).getResultList();
        }

        /**
         * {@inheritDoc}
         */
        @SuppressWarnings("unchecked")
        public List<Object> load(final List<Object> identifiers) {
            if (identifiers == null) {
                throw new IllegalArgumentException("Parameter identifiers(List) must not be null.");
            }

            final String queryString = getSelectClause() + " where e."
                    + identifierHelper.getIdentifierName(entityClass) + " in (:identifiers)";
            final Query query = entityManager.createQuery(queryString);
            query.setParameter("identifiers", identifiers);
            return query.getResultList();
        }

        private String getSelectClause() {
            return "select e from " + entityClass.getName() + " e";
        }
    }
}
